25.Gün - SwiftUI Önemli Noktalar
Table of Contents
16-24.günlerde gerçekleştirdiğimiz projelerde öğrendiğimiz önemli noktaları bugünkü yazımızda özetleyeceğiz.
Bu yazımızda 3 önemli konu üzerinde duracağız. Bunlar;
- Struct ve Class
- ForEach
- Binding ile çalışmak
Struct ve Class #
Struct ve class arasında beş fark olduğuna daha önceki yazımızda değinmiştik.
- Class’ların memberwise initializer’ı yoktur, struct’ların varsayılan olarak memberwise initializer’ı bulunmaktadır.
- Class’lar işlevsellik oluşturmak için kalıtım kullanabilir, struct’lar kullanamaz.
- Bir class’ı kopyalarsak, her iki kopyada aynı veriye işaret eder, struct’arın kopyaları her zaman benzersizdir.
- Class’ların deinitializer’ı olabilir, struct’ların olamaz.
- Constant class’ların değişken property’lerini değiştirebiliriz, sabit struct’ların içindeki property’ler, property’lerin sabit veya değişken olmasına bakılmaksızın sabittir.
Apple’ın orijinal programlama dili Objective-C’de hemen hemen her şey için sınıflar kullanılırdı. Çünkü bu, işleyiş biçimine dahil edildiğinden başka bir seçeneğimiz yoktu.
Swift’te ise bu seçimi yapabiliyoruz. Bu seçimi de kodumuzdaki özel durumlara göre yapıyoruz. Birisi kodumuzu okuduğunda amacımızı tam olarak anlamalıdır. Çoğu zaman struct kullanıyorsak, belirli bir yerde class’a geçmek bazı niyetleri ifade eder. Her zaman class kullanıyorsak, bu ayrım kaybolur.
SwiftUI’nin farklarından biri de struct ve class’ları nasıl kullandığımızı tamamen tersine çevirmesidir. UIKit’de struct’ları veri için, class’ları ise kullanıcı arayüzleri için kullanırdık, SwiftUI’de bunun tam tersi söz konusudur.
ForEach ile Çalışmak #
Bahsedeceğimiz ikinci konu ise ForEach
örneğin şöyle bir kodda kullanabiliriz;
ForEach(0 ..< 100) { number in
Text("Row \(number)")
}
ForEach
, SwiftUI’deki diğer çoğu şey gibi bir view’dır. Bir döngü içinde başka view’lar oluşturmamızı sağlar.
Şöyle bir string array’imiz olduğunu düşünelim;
let agents = ["Cyril", "Lana", "Pam", "Sterling"]
Bunların üzerinde döngü yaparak, nasıl text view’lar oluşturabiliriz?
Seçeneklerimizden biri halihazırdaki yapımızı kullanmaktır;
VStack {
ForEach(0..<agents.count) {
Text(agents[$0])
}
}
Ancak SwiftUI bize ikinci bir alternatif sunuyor; doğrudan array üzerinde döngü yapabiliriz.
Dört öğeli bir Array üzerinde döngü yaparsak, dört view oluşturacağız, ancak body yeniden çağırılırsa ve array artık beş öğe içeriyorsa, SwiftUI’nin hangi view’ın yeni olduğunu bilmesi gerekir. SwiftUI’nin yapmak isteyeceği son şey, küçük bir değişiklik yapıldığında tüm layout’u çöpe atmak ve her şeye sıfırdan başlamaktır. Bunun yerine mümkün olan en az miktarda işi yapmak ister: mevcut dört view’ı yerinde bırakmak ve yalnızca beşinciyi eklemek ister.
Böylece, Swift’in Array’deki değerleri nasıl tanımlayabileceğine geri dönüyoruz. 0..<5
veya 0..<agents.count
gibi bir aralık kullandığımızda, Swift her öğenin benzersiz olduğundan emindi çünkü aralıktaki sayıları kullanacaktı. (Her sayı döngüde yalnızca bir kez kullanılıyordu bu yüzden kesinlikle benzersiz olacaktı)
String Array’de bu artık mümkün değil, ancak her değerin benzersiz olduğunu açıkça görebiliyoruz: ["Cyril", "Lana", "Pam", "Sterling"]
içindeki değerler tekrar etmiyor. O halde SwiftUI’ye döngüdeki her bir view’ı benzersiz bir şekilde tanımlamak için kullanılabilecek şeyin stringlerin kendileri “Cyril”, “Lana” vb. olduğunu söyleyebiliriz.
Kodu şu şekilde yazabiliriz;
VStack {
ForEach(agents, id: \.self) {
Text($0)
}
}
Bu nedenle, tamsayılar üzerinde döngü yapmak ve bu tamsayıları array üzerinden okuma yapmak için kullanmak yerine, Array’deki öğeleri doğrudan okuyoruz.
SwiftUI’de ile ilerledikçe, Identifiable
protokolünü kullanarak, view’ları tanımlamanın üçüncü bir yolunu inceleyeceğiz.
Bindings ile Çalışmak #
Picker
ve TextField
gibi kontrolleri kullandığımızda, $propertyName
kullanarak @State
property’ye two-way binding oluştururuz. Bu basit property’ler için harika çalışır, ancak bazen daha gelişmiş bir şey isteyebiliriz. Mevcut değeri hesaplamak için bazı logical işlemler yapmak istiyorsak? ya da bir değer saklamaktan daha fazlasını yapmak istersek?
Değişikliklere tepki vermek için Swift’in didSet
property observer’ını kullanmayı deneyebiliriz, fakat bu bizi hayal kırıklığına uğratır. İşte bu noktada özel binding’ler devreye girer: @State
binding gibi kullanılabilirler.
Binding sihirli değildir. @State
bizim için bazı sıkıcı şablon kodlarını ortadan kaldırır, ancak istersek binding’leri elle oluşturmak ve yönetmek mümkündür.
SwiftUI’nin bizim için yaptığı her şey elle yapılabilir. Otomatik çözüme güvenmek neredeyse her zaman daha iyi olsa da, perde arkasına bir göz atmak gerçekten yararlı olabilir, böylece bizim adımıza ne yaptığını anlayabiliriz.
İlk olarak, değeri başka bir @State
property’de saklayan ve bunu geri okuyan en basit custom binding biçimine bakalım;
struct ContentView: View {
@State private var selection = 0
var body: some View {
let binding = Binding(
get: { selection },
set: { selection = $0 }
)
return VStack {
Picker("Select a number", selection: binding) {
ForEach(0..<3) {
Text("Item \($0)")
}
}
.pickerStyle(.segmented)
}
}
}
Dolayısıyla, bu binding etkin bir şekilde sadece bir geçiş görevi görür. Herhangi bir veriyi kendisi depolamaz ve hesaplamaz, ancak yalnızca kullanıcı arayüzümüz ile manipüle edilen temel durum değeri arasında bir dolgu görevi görür.
Ancak, picker’ın artık selection: binding
kullanılarak yapıldığına dikkat edin artık $
işaretine gerek yoktur. Burada açıkça two-way binding istememize gerek yok çünkü zaten var.
İsteseydik, tek bir değer aktarmaktan daha fazlasını yapan daha gelişmiş bir binding oluşturabilirdik. Örneğin, üç tane toggle switch olan bir formumuz olduğunu düşünelim; kullanıcı şartlar ve koşulları kabul ediyor mu, gizlilik politikasını kabul ediyor mu ve kargo hakkında e-posta almayı kabul ediyor mu?
Bunu üç Boolean @State
property olarak gösterebiliriz;
@State var agreedToTerms = false
@State var agreedToPrivacyPolicy = false
@State var agreedToEmails = false
Kullanıcı toggle switch’lerin hepsini elle değiştirebilse de, hepsini aynı anda yapmak için özel bir binding kullanabiliriz. Bu binding, Boolean’ların üçü de doğruysa olur, ancak değiştirilirse hepsini günceller, bunun gibi;
let agreedToAll = Binding(
get: {
agreedToTerms && agreedToPrivacyPolicy && agreedToEmails
},
set: {
agreedToTerms = $0
agreedToPrivacyPolicy = $0
agreedToEmails = $0
}
)
Şimdi dört toggle switch oluşturabiliriz: her biri ayrı Boolean ve aynı anda üçünü de kabul eden veya etmeyen bir control switch.
struct ContentView: View {
@State private var agreedToTerms = false
@State private var agreedToPrivacyPolicy = false
@State private var agreedToEmails = false
var body: some View {
let agreedToAll = Binding<Bool>(
get: {
agreedToTerms && agreedToPrivacyPolicy && agreedToEmails
},
set: {
agreedToTerms = $0
agreedToPrivacyPolicy = $0
agreedToEmails = $0
}
)
return VStack {
Toggle("Agree to terms", isOn: $agreedToTerms)
Toggle("Agree to privacy policy", isOn: $agreedToPrivacyPolicy)
Toggle("Agree to receive shipping emails", isOn: $agreedToEmails)
Toggle("Agree to all", isOn: agreedToAll)
}
}
}
Custom binding çok sık kullanacağımız bir şey değildir, ancak perde arkasına bakmak ve neler olup bittiğini anlamak için zaman ayırmak önemlidir.
Taş, Kağıt, Makas oyununa ait projeye aşağıdan ulaşabilirsiniz.
GitHub - GorkemGuray/ROPS: 100 Days of SwiftUI - Milestone: Projects 1-3
Bu yazıyı İngilizce olarak da okuyabilirsiniz.
You can also read this article in English.